package org.rainbowinverted.charging.device.connect.bl.server;

import cn.hutool.core.util.HexUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.util.internal.logging.Slf4JLoggerFactory;
import lombok.extern.slf4j.Slf4j;
import org.rainbowinverted.charging.device.connect.bl.server.constants.BlConstants;
import org.rainbowinverted.charging.device.connect.bl.server.decode.BlBkvDecoder;
import org.rainbowinverted.charging.device.connect.bl.server.encode.BlBkvEncoder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @author huaiping
 */
@Slf4j
@Component
public class BlDeviceConnectServer implements ApplicationRunner {

    private final BlBkvDecoder blBkvDecoderHandler;
    private final BlBkvEncoder blBkvEncoderHandler;
    @Value("${rainbowinverted.device-connect.netty.port:7084}")
    private Integer port;
    private ChannelFuture channelFuture;
    public BlDeviceConnectServer(BlBkvDecoder blBkvDecoderHandler,
                                 BlBkvEncoder blBkvEncoderHandler) {
        this.blBkvDecoderHandler = blBkvDecoderHandler;
        this.blBkvEncoderHandler = blBkvEncoderHandler;
    }

    public synchronized void start() {
        InternalLoggerFactory.setDefaultFactory(Slf4JLoggerFactory.INSTANCE);
        if (Objects.isNull(channelFuture)) {
            NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
            NioEventLoopGroup workGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);

            try {

                ServerBootstrap server = new ServerBootstrap();
                // 拆包分隔符
                ByteBuf delimiter = Unpooled.copiedBuffer(HexUtil.decodeHex(BlConstants.END_SYMBOL_BL));
                // 内存池化
                ByteBufAllocator allocator = ByteBufAllocator.DEFAULT;
                server.group(bossGroup, workGroup)
                        .channel(NioServerSocketChannel.class)
                        .option(ChannelOption.ALLOCATOR, allocator)
                        .childOption(ChannelOption.ALLOCATOR, allocator)
                        .childOption(ChannelOption.SO_RCVBUF, 2048)
                        .childOption(ChannelOption.SO_SNDBUF, 2048)
                        .childOption(ChannelOption.TCP_NODELAY, true)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) {
                                ch.pipeline()
                                        .addLast("loggingHandler", new LoggingHandler(LogLevel.INFO))
                                        .addLast("readTimeoutHandler", new ReadTimeoutHandler(120))
                                        .addLast("delimitDecoder", new DelimiterBasedFrameDecoder(2048,
                                                false, delimiter))
                                        .addLast("blBkvEncoder", blBkvEncoderHandler)
                                        .addLast("blBkvDecoder", blBkvDecoderHandler);
                            }
                        });

                this.channelFuture = server.bind(port).sync();
                log.info("start bl device connect server success on port: {}", port);
                // 优雅停机
                channelFuture.channel().closeFuture().sync();
            } catch (Exception e0) {
                log.error("start bl device connect server fail", e0);
                try {
                    bossGroup.shutdownGracefully().sync();
                    workGroup.shutdownGracefully().sync();
                } catch (Exception e1) {
                    log.error("shutdown netty exception", e1);
                }
            }
        }
    }

    @Override
    public void run(ApplicationArguments args) {
        start();
    }
}
